package com.qd.panda.service.stroke.order;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.math.Money;
import com.cdqidi.constant.ConstantDto;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.RedisTemplateUtil;
import com.cdqidi.util.SpringContextHolder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qd.common.panda.constant.PandaConstant;
import com.qd.common.panda.domain.entity.carpark.CarParkGasStationDTO;
import com.qd.common.panda.domain.entity.carpark.calculate.CalculatePriceDTO;
import com.qd.common.panda.domain.entity.carpark.calculate.CalculatePriceDetailDTO;
import com.qd.common.panda.domain.entity.order.GasCalculatePriceOrderDTO;
import com.qd.common.panda.domain.entity.stroke.UserStrokeOrderDTO;
import com.qd.common.panda.domain.entity.stroke.UserStrokeOrderGasDTO;
import com.qd.common.panda.domain.entity.stroke.preferential.UserStrokeOrderPreferentialLogDTO;
import com.qd.common.panda.domain.enums.GasStationCompanyNameEnum;
import com.qd.common.panda.domain.enums.PreferentialCategoryEnum;
import com.qd.common.sys.domain.entity.log.SendLogDTO;
import com.qd.common.sys.domain.entity.login.LoginUserDTO;
import com.qd.common.sys.domain.login.AppUserExtend;
import com.qd.common.sys.domain.login.LoginUser;
import com.qd.common.sys.event.SendLogEvent;
import com.qd.common.sys.util.SecurityUtils;
import com.qd.panda.gas.station.base.GasHttpService;
import com.qd.panda.gas.station.base.GasHttpServiceInitApplicationContextAware;
import com.qd.panda.gas.station.req.PayNotifyReqDTO;
import com.qd.panda.gas.station.req.PayReqDTO;
import com.qd.panda.gas.station.resp.PayRespDTO;
import com.qd.panda.model.*;
import com.qd.panda.service.card.CardPlanHolderService;
import com.qd.panda.service.card.UserCardHolderService;
import com.qd.panda.service.integral.UserIntegralService;
import com.qd.panda.service.park.CarParkGasStationPriceService;
import com.qd.panda.service.park.CarParkGasStationService;
import com.qd.panda.service.park.CarParkService;
import com.qd.panda.service.park.GasStationHolderService;
import com.qd.panda.service.plate.UserPlateRelationService;
import com.qd.panda.service.stroke.DisplayUtil;
import com.qd.system.model.BaseUserInfo;
import com.qd.system.service.user.BaseUserInfoService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * 加油
 *
 * @author sjk
 * @create 2022-10-12 13:35
 * @since 1.0.0
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class GasOrderService {
    /**
     * 如果使用了卡卷信息
     * 那么存储订单和优惠劵ID的关系
     */
    public static final String ORDER_ID_TO_CARD_ID = "order_id_{0}";
    private static final String PREFIX_ID = "gas_pay_notify_req_order_id_{0}";
    private final UserStrokeOrderService userStrokeOrderService;
    private final UserStrokeOrderGasService userStrokeOrderGasService;
    private final UserStrokeOrderPreferentialLogService userStrokeOrderPreferentialLogService;
    private final UserPlateRelationService userPlateRelationService;
    private final CarParkService carParkService;
    private final CarParkGasStationService carParkGasStationService;
    private final CarParkGasStationPriceService carParkGasStationPriceService;
    private final BaseUserInfoService baseUserInfoService;
    private final UserIntegralService userIntegralService;
    private final CardPlanHolderService cardPlanHolderService;
    private final UserCardHolderService userCardHolderService;
    private final StringRedisTemplate stringRedisTemplate;
    private final ObjectMapper objectMapper;

    /**
     * 优惠卷优惠
     *
     * @param orderId               订单ID
     * @param cardId                优惠劵ID
     * @param stationCalculatePrice 对象
     */
    public void buildCardInfo(String orderId, String cardId, CalculatePriceDTO stationCalculatePrice) {
        List<CalculatePriceDetailDTO> detailList = stationCalculatePrice.getDetailList();
        if (null != detailList && !detailList.isEmpty()) {
            //因为使用了优惠券，实际优惠金额就是优惠劵的金额，
            //加油站的立减只是为了对账
            //所以设置flag=1，表示不参与优惠
            for (CalculatePriceDetailDTO calculatePriceDetailDTO : detailList) {
                if (calculatePriceDetailDTO.getPreferentialCategory().equals(PreferentialCategoryEnum.PREFERENTIAL_1.getCode())) {
                    calculatePriceDetailDTO.setFlag(1);
                }
            }
        }
        CardInfo cardInfo = userCardHolderService.validatorAndGetCardInfo(cardId, stationCalculatePrice.getPrice(), PandaConstant.CARD_TYPE_GAS, null);
        BigDecimal amount = userCardHolderService.buildCard(cardId, stationCalculatePrice.getPrice(), null);
        CalculatePriceDetailDTO calculatePriceDetailDTO = new CalculatePriceDetailDTO();
        if (null != cardInfo.getDiscount() && cardInfo.getDiscount() > 0) {
            calculatePriceDetailDTO.setCustomerDiscount(cardInfo.getDiscount());
        }
        calculatePriceDetailDTO.setFlag(0);
        calculatePriceDetailDTO.setPrice(stationCalculatePrice.getPrice());
        calculatePriceDetailDTO.setCalculatePrice(amount);
        calculatePriceDetailDTO.setDiscountAmount(new Money(stationCalculatePrice.getPrice()).subtract(new Money(amount)).getAmount());
        calculatePriceDetailDTO.setServiceFee(new BigDecimal("0"));
        calculatePriceDetailDTO.setPlatform(PandaConstant.PLATFORM_PANDA_PARK);
        calculatePriceDetailDTO.setPreferentialCategory(PreferentialCategoryEnum.PREFERENTIAL_3.getCode());
        stationCalculatePrice.addCalculatePriceDetailDTO(calculatePriceDetailDTO);
        stationCalculatePrice.setCalculatePrice(amount);
        stationCalculatePrice.setDiscountAmount(calculatePriceDetailDTO.getDiscountAmount());
        stringRedisTemplate.opsForValue().set(RedisTemplateUtil.getRedisCacheKey(ORDER_ID_TO_CARD_ID, orderId), cardId);
        //修改优惠劵使用状态
        cardPlanHolderService.updateUseCard(cardId, orderId, PandaConstant.USE_STATE_2);
    }

    /**
     * 积分抵扣
     *
     * @param integral              积分
     * @param stationCalculatePrice 对象
     */
    public void buildUserIntegral(String orderId, String integral, CalculatePriceDTO stationCalculatePrice) {
        BigDecimal integralBigDecimal = new BigDecimal(integral);
        Money integralMoney = new Money(integral);
        //计算积分抵扣的金额是多少
        integralMoney = integralMoney.multiplyBy(GasStationHolderService.DEDUCTION);
        boolean setIntegral = false;
        if (null == stationCalculatePrice.getIntegralAmount()) {
            //如果不为空，说明已经计算过了，无需重复计算
            setIntegral = true;
        } else {
            if (stationCalculatePrice.getIntegralAmount().compareTo(BigDecimal.ZERO) < BigDecimal.ONE.signum()) {
                setIntegral = true;
            }
        }
        if (setIntegral) {
            stationCalculatePrice.setCalculatePrice(new Money(stationCalculatePrice.getCalculatePrice()).subtract(integralMoney).getAmount());
            stationCalculatePrice.setIntegralAmount(integralBigDecimal);
        }
        CalculatePriceDetailDTO calculatePriceDetailDTO = getCalculatePriceDetailDTO(integralBigDecimal, integralMoney);
        stationCalculatePrice.addCalculatePriceDetailDTO(calculatePriceDetailDTO);

        //生成积分明细,扣除个人积分，生成使用明细
        final String userId = SecurityUtils.getLoginUser().getUserId();
        userIntegralService.saveIntegral(userId, integralBigDecimal, "加油使用熊猫豆：" + integral + "个,抵扣金额: " + integralMoney.getAmount() + "元", PandaConstant.POINT_TYPE_GAS, 0, orderId);
    }

    private CalculatePriceDetailDTO getCalculatePriceDetailDTO(BigDecimal integralBigDecimal, Money integralMoney) {
        CalculatePriceDetailDTO calculatePriceDetailDTO = new CalculatePriceDetailDTO();
        calculatePriceDetailDTO.setCustomerDiscount(GasStationHolderService.DEDUCTION);
        calculatePriceDetailDTO.setPrice(integralBigDecimal);
        calculatePriceDetailDTO.setCalculatePrice(integralBigDecimal);
        calculatePriceDetailDTO.setDiscountAmount(integralMoney.getAmount());
        calculatePriceDetailDTO.setServiceFee(new BigDecimal("0"));
        calculatePriceDetailDTO.setPlatform(PandaConstant.PLATFORM_PANDA_PARK);
        calculatePriceDetailDTO.setPreferentialCategory(PreferentialCategoryEnum.PREFERENTIAL_4.getCode());
        return calculatePriceDetailDTO;
    }

    @SneakyThrows
    public void buildToRedis(String orderId, String thirdOrderId, String gasStationCompanyId,CalculatePriceDTO stationCalculatePrice,GasCalculatePriceOrderDTO dto ) {
        BigDecimal price = stationCalculatePrice.getPrice();
        BigDecimal calculatePrice = stationCalculatePrice.getRefuelingAmount();
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, orderId);
        final String mobile = SecurityUtils.getLoginUser().getUser().getAppUserExtend().getMobile();
        PayNotifyReqDTO payNotifyReqDTO = new PayNotifyReqDTO()
                .setMobile(mobile)
                .setStationId(Integer.valueOf(carParkGasStationService.getById(dto.getParkId()).getStationId()))
                .setOctaneNumber(dto.getOctaneNumber())
                .setNozzleNumber(dto.getNozzleNumber())
                .setThirdPartyOrderId(orderId)
                .setPrice(price)
                .setPayAmount(calculatePrice)
                .setGasStationCompanyId(gasStationCompanyId);
        if(StringUtils.hasLength(thirdOrderId)){
            payNotifyReqDTO.setOrderId(thirdOrderId);
        }
        stringRedisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(payNotifyReqDTO), 1, TimeUnit.DAYS);
    }

    public PayRespDTO createGasOrder(String orderId, CarParkGasStation carParkGasStation, GasCalculatePriceOrderDTO dto, BigDecimal price, BigDecimal calculatePrice) {
        if(carParkGasStation.getGasStationCompanyName().equals(GasStationCompanyNameEnum.E_JIA_YOU.getCode())){
            long start = System.currentTimeMillis();
            final GasHttpService gasHttpService = GasHttpServiceInitApplicationContextAware.getGasHttpService(carParkGasStation.getGasStationCompanyName());
            SendLogDTO sendLogDTO = new SendLogDTO();
            sendLogDTO.setBid(orderId);
            sendLogDTO.setAddTime(LocalDateTime.now());
            sendLogDTO.setLogType(PandaConstant.LOG_TYPE_GAS_CREATE_ORDER);
            sendLogDTO.setSendState(ConstantDto.SF1);
            final PayReqDTO payReqDTO = new PayReqDTO().setStationId(carParkGasStation.getStationId())
                    .setTotalAmount(price)
                    .setCalculatePrice(calculatePrice)
                    .setNozzleNumber(dto.getNozzleNumber())
                    .setOctaneNumber(dto.getOctaneNumber())
                    .setUserId(SecurityUtils.getLoginUser().getUserId())
                    .setThirdPartyOrderId(orderId)
                    .setSendLogDTO(sendLogDTO);
            try {
                try {
                    return gasHttpService.pay(payReqDTO);
                } finally {
                    long end = System.currentTimeMillis();
                    sendLogDTO.setTimeConsumer(end - start);
                    SpringContextHolder.publishEvent(new SendLogEvent(sendLogDTO));
                }
            } catch (Exception e) {
                final String message = ExceptionUtil.getMessage(e);
                sendLogDTO.setErrMsg(message);
                sendLogDTO.setSendState(ConstantDto.SF0);
                throw e;
            }
        }
        return null;
    }


    public void deleteRedis(String orderId) {
        stringRedisTemplate.delete(RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, orderId));
    }

    @SneakyThrows
    @Transactional(readOnly = true)
    public PayNotifyReqDTO getGasPayNotifyReqDTO(String orderId) {
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, orderId);
        final String s = stringRedisTemplate.opsForValue().get(key);
        if (null == s) {
            UserStrokeOrderPreferentialLogDTO query = new UserStrokeOrderPreferentialLogDTO();
            query.setOrderId(orderId);
            query.setPreferentialCategory(PreferentialCategoryEnum.PREFERENTIAL_1.getCode());
            final UserStrokeOrderPreferentialLog userStrokeOrderPreferentialLog = userStrokeOrderPreferentialLogService.getOne(query);
            query.freeData();

            final UserStrokeOrder userStrokeOrder = userStrokeOrderService.getById(orderId);
            if (null != userStrokeOrder) {
                final BaseUserInfo baseUserInfo = baseUserInfoService.getById(userStrokeOrder.getUserId());
                if (null != baseUserInfo) {
                    CarParkGasStation carParkGasStation = carParkGasStationService.getById(userStrokeOrder.getParkId());
                    String gasStationCompanyName = carParkGasStation.getGasStationCompanyName();
                    PayNotifyReqDTO payNotifyReqDTO = new PayNotifyReqDTO();
                    payNotifyReqDTO.setMobile(baseUserInfo.getMobile());
                    payNotifyReqDTO.setOrderId(userStrokeOrderPreferentialLog.getThirdPartyOrderId());
                    payNotifyReqDTO.setThirdPartyOrderId(orderId);
                    UserStrokeOrderGas strokeOrderGas = userStrokeOrderGasService.getById(orderId);
                    payNotifyReqDTO.setOctaneNumber(strokeOrderGas.getOctaneNumber());
                    payNotifyReqDTO.setNozzleNumber(strokeOrderGas.getNozzleNumber());
                    payNotifyReqDTO.setStationId(Integer.valueOf(carParkGasStation.getStationId()));
                    payNotifyReqDTO.setPrice(userStrokeOrder.getOrderAmount());
                    //订单金额-优惠金额=实际支付金额
                    Money payAmount = new Money(userStrokeOrderPreferentialLog.getPrice()).subtract(new Money(userStrokeOrderPreferentialLog.getDiscountAmount()));
                    payNotifyReqDTO.setPayAmount(payAmount.getAmount());
                    payNotifyReqDTO.setGasStationCompanyId(gasStationCompanyName);
                    return payNotifyReqDTO;
                }
            }
        } else {
            return objectMapper.readValue(s, PayNotifyReqDTO.class);
        }
        return null;
    }


    /**
     * 保存订单信息
     *
     * @param orderId 订单ID
     * @param parkId  加油站ID
     * @param now     当前时间
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveOrder(String orderId, String parkId, LocalDateTime now,
                          CalculatePriceDTO stationCalculatePrice) {
        //订单金额
        BigDecimal orderAmount = stationCalculatePrice.getPrice();
        //实际支付金额
        BigDecimal payAmount = stationCalculatePrice.getCalculatePrice();
        //优惠金额
        BigDecimal discountAmount = (new Money(orderAmount).subtract(new Money(payAmount))).getAmount();
        CarPark carPark = Optional.ofNullable(carParkService.getById(parkId)).orElseThrow(() -> new ApiException("加油站不存在"));
        final String userId = SecurityUtils.getLoginUser().getUserId();
        final String plateNumber = userPlateRelationService.getPlateNumberByUserId(userId);
        final String nickName = Optional.ofNullable(SecurityUtils.getLoginUser()).map(LoginUser::getUser).map(LoginUserDTO::getAppUserExtend).map(AppUserExtend::getNickName).orElse("");
        final String promotionCode = Optional.ofNullable(SecurityUtils.getLoginUser()).map(LoginUser::getUser).map(LoginUserDTO::getAppUserExtend).map(AppUserExtend::getPromotionCode).orElse("");
        UserStrokeOrderDTO userStrokeOrderDTO = new UserStrokeOrderDTO();
        userStrokeOrderDTO.setOrderId(orderId);
        userStrokeOrderDTO.setBid(orderId);
        userStrokeOrderDTO.setStartTime(now);
        userStrokeOrderDTO.setNickName(nickName);
        userStrokeOrderDTO.setOrderAmount(orderAmount);
        userStrokeOrderDTO.setPayAmount(payAmount);
        userStrokeOrderDTO.setOnLinePayAmount(payAmount);
        userStrokeOrderDTO.setDiscountAmount(discountAmount);
        userStrokeOrderDTO.setPayStatus(DisplayUtil.PAY_STATUS_0);
        userStrokeOrderDTO.setOrderType(DisplayUtil.ORDER_TYPE_4);
        userStrokeOrderDTO.setTripType(DisplayUtil.TRIP_TYPE_2);
        userStrokeOrderDTO.setParkId(carPark.getPId());
        userStrokeOrderDTO.setParkName(carPark.getName());
        userStrokeOrderDTO.setEtcPay(ConstantDto.SF0);
        userStrokeOrderDTO.setUserId(userId);
        userStrokeOrderDTO.setPlateNumber(plateNumber);
        if (StringUtils.hasLength(promotionCode)) {
            userStrokeOrderDTO.setPromotionCode(promotionCode);
        }
        userStrokeOrderService.save(userStrokeOrderDTO);
        userStrokeOrderDTO.freeData();
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveOrderGas(String orderId, GasCalculatePriceOrderDTO gasCalculatePriceOrderDTO, BigDecimal oilMass) {
        final CarParkGasStationDTO carParkGasStation = Optional.ofNullable(carParkGasStationService.getByIdDto(gasCalculatePriceOrderDTO.getParkId())).orElseThrow(() -> new ApiException("加油站信息不存在"));
        UserStrokeOrderGasDTO userStrokeOrderGasDTO = new UserStrokeOrderGasDTO();
        userStrokeOrderGasDTO.setOrderId(orderId);
        userStrokeOrderGasDTO.setGasStationCompanyId(carParkGasStation.getGasStationCompanyName());
        userStrokeOrderGasDTO.setGasStationCompanyName(carParkGasStation.getGasStationCompanyNameDisplay());
        userStrokeOrderGasDTO.setOctaneNumber(gasCalculatePriceOrderDTO.getOctaneNumber());
        userStrokeOrderGasDTO.setNozzleNumber(gasCalculatePriceOrderDTO.getNozzleNumber());
        final CarParkGasStationPrice carParkGasStationPrice = carParkGasStationPriceService.getUniqueByOctaneNumberAndPId(gasCalculatePriceOrderDTO.getOctaneNumber(), gasCalculatePriceOrderDTO.getParkId());
        if (null != carParkGasStationPrice) {
            userStrokeOrderGasDTO.setPrice(carParkGasStationPrice.getPrice());
        }
        if (null == oilMass) {
            //说明是壳牌的，全部自己计算
            //由于油号和枪号的匹配并不是标准的，所以这里的单价和加油量都只是参考而已
            if (null != carParkGasStationPrice) {
                oilMass = new BigDecimal(gasCalculatePriceOrderDTO.getPrice()).divide(carParkGasStationPrice.getPrice(), 2, RoundingMode.HALF_UP);
            }
        }
        Optional.ofNullable(oilMass).ifPresent(oil -> userStrokeOrderGasDTO.setOilVolume(oil.doubleValue()));
        if (null != carParkGasStationPrice) {
            carParkGasStationPrice.freeData();
        }
        userStrokeOrderGasDTO.setStationType(carParkGasStation.getStationType());
        userStrokeOrderGasService.save(userStrokeOrderGasDTO);
        userStrokeOrderGasDTO.freeData();
        carParkGasStation.freeData();
    }

    @Transactional(rollbackFor = Exception.class)
    public void savePreferentialLog(String orderId, String thirdPartyOrderId,
                                    LocalDateTime now, CalculatePriceDTO stationCalculatePrice) {
        final List<CalculatePriceDetailDTO> detailList = stationCalculatePrice.getDetailList();
        if (null != detailList && !detailList.isEmpty()) {
            for (CalculatePriceDetailDTO calculatePriceDetailDTO : detailList) {
                UserStrokeOrderPreferentialLogDTO userStrokeOrderPreferentialLogDTO = new UserStrokeOrderPreferentialLogDTO();
                userStrokeOrderPreferentialLogDTO.setOrderId(orderId);
                if (StringUtils.hasLength(thirdPartyOrderId)) {
                    userStrokeOrderPreferentialLogDTO.setThirdPartyOrderId(thirdPartyOrderId);
                }
                userStrokeOrderPreferentialLogDTO.setAddTime(now);
                userStrokeOrderPreferentialLogDTO.setPreferentialCategory(calculatePriceDetailDTO.getPreferentialCategory());
                userStrokeOrderPreferentialLogDTO.setIdx(PreferentialCategoryEnum.getIdxByCode(calculatePriceDetailDTO.getPreferentialCategory()));
                userStrokeOrderPreferentialLogDTO.setPlatform(calculatePriceDetailDTO.getPlatform());
                userStrokeOrderPreferentialLogDTO.setPrice(calculatePriceDetailDTO.getPrice());
                userStrokeOrderPreferentialLogDTO.setDiscountAmount(calculatePriceDetailDTO.getDiscountAmount());
                userStrokeOrderPreferentialLogDTO.setCustomerDiscount(calculatePriceDetailDTO.getCustomerDiscount());
                userStrokeOrderPreferentialLogDTO.setServiceCharge(calculatePriceDetailDTO.getServiceFee());
                userStrokeOrderPreferentialLogDTO.setFlag(calculatePriceDetailDTO.getFlag());
                userStrokeOrderPreferentialLogService.save(userStrokeOrderPreferentialLogDTO);
                userStrokeOrderPreferentialLogDTO.freeData();
            }
        }
    }
}